#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getline.h>
#include <ctype.h>
#include <errno.h>
#include <errors.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "trapezoidal.h"

#define MAX_BUF 512
//int isFunAvailable (char *strPtr);
//int checkNum (char *strPtr, int isDouble);

/* Pass 0/1 as 2nd argument to checkNum funtion to check if it is double or not */
int checkNum (char *strPtr, int isDouble){
	int i = 0;
	int flag = 0;

	if (strPtr[0] == '.'){
		return 1;
	}

	for (i = 0; i < strlen (strPtr); i++){
		if (strPtr[i] == '.'){
			if (flag == 0)
				flag = 1;
			else
				return 1;
		}
	}

	for (i = 0; i < strlen (strPtr); i ++){
		if (isDouble == 1){

			if (!isdigit((int)strPtr[i]) && strPtr[i] != 'e' && strPtr[i] != 'E' && strPtr[i] != '+' && strPtr[i] != '.' && 
					strPtr[i] != '-'){
				return 1;
			}
		}
		else{
			if (!isdigit((int)strPtr[i])){
				return 1;
			}

		}
		/* Check for same operator does not occur. eg. 12++1, 12-+1, etc */
		if (strPtr[i] == '+' || strPtr[i] == '.' || strPtr[i] == '-'){
			if (!isdigit ((int)strPtr[i+1] )) {
				return 1;
			}
			if ( i != 0 && strPtr[i - 1] == ' ' && strPtr[i-1] == 'e'){
				return 1;
			}
		}
		if (strPtr[i] == 'e' || strPtr[i] == 'E'){
			if (!isdigit((int)strPtr[i+1]) && strlen (strPtr) <= i + 1){
				return 1;
			}
		}
	} 
	return 0;
}

Params getCmdLineInput(int level){
	int i = 0;
	char *line = NULL, *strPtr = NULL, *pEnd = NULL;
	int len = 0,  flag = 0;

	Params prm;

	if (level == 0){
		fprintf (stderr, "\nEnter the values for \nFN LO_BOUND HI_BOUND N_INTERVALES\n(Type 'quit' to exit)\n");
	}

	line = (char *)malloc (sizeof (char));
	if (!line){
		fprintf (stderr, "Not enough memory\n");
		exit(1);
	}
	if (getline (stdin, &line, &len) == -1){
		fprintf (stderr, "ERROR: Unable to read line\n");
		exit(1);
	}

	/* scan line and parse individual entry */
	i = 0;
	strPtr = strtok (line, " \n");

	if(strcmp(strPtr, "quit") == 0){
		prm.error = 1;
		return prm;
	} 

	while (strPtr != NULL){

		if ( i == 0){
			//Check if the funtion is available in fns[] 
			if ( getFnForString (strPtr)){
				prm.fnName = (char *)malloc (sizeof (char) * strlen (strPtr));
				strcpy (prm.fnName, strPtr);
			}
			else{
				fprintf (stderr, "\nERROR: Function \"%s\" is not defined\n", strPtr);
				flag = 1;
				prm.error = -1;
				return prm;
			}
		}

		if ( i == 1){
			/* Pass 1 as 2nd argument to checkNum funtion if the entered value is double */
			if (checkNum(strPtr, 1) == 0){
				prm.lo_bound = strtod (strPtr, &pEnd);	  
			}
			else{
				fprintf (stderr, "\nERROR: Invalid LO_BOUND value : %s\n", strPtr);
				flag = 1;
				prm.error = -1;
				return prm;
			}
		}

		if ( i == 2){
			if (checkNum (strPtr, 1) == 0){
				prm.hi_bound = strtod (strPtr, &pEnd);
			}
			else{
				fprintf (stderr, "\nERROR: Invalid HI_BOUND value : %s\n", strPtr);
				flag = 1;
				prm.error = -1;
				return prm;
			}
		}

		if ( i == 3){
			/* Pass 0 as 2nd argument to checkNum funtion if the entered value is not double */
			if (checkNum (strPtr, 0) == 0){
				prm.n_intervals = atoi (strPtr);
			}
			else{
				fprintf (stderr, "\nERROR: Invalid N_INTERVALS : %s\n", strPtr);
				flag = 1;
				prm.error = -1;
				return prm;
			}
		}

		if ( i >= 4){
			fprintf (stderr, "\nERROR: Illegal input %s\n", strPtr);
			flag = 1;
			prm.error = -1;
			return prm;
		}

		i++;
		/* Read next argument from line by using strtok */
		strPtr = strtok (NULL, " \n");
	}

	i--;
	if (i != 3){
		fprintf (stderr, "\nERROR: Insufficient values provided\n");
		prm.error = -1;
		return prm;
	}

	/* Free the line before returning. Free it only if it has data */
	if (line)
		free (line);

	return prm;
}


/*Get the data from command line and divide it and send to children for processing */
void getData (int **parentInFD, int **parentRtFD, int children, int level, int debug){
	char rbuf[512] = "", wbuf[512] = "";
	int i = 0, j = 0, status;
	pid_t pid;
	double result = 0,tmp = 0;
	Params prm; 
	char dBuf[512];

	while(1){

		prm.error = 0;
		prm = getCmdLineInput(level);
		if (prm.error == -1){
			fprintf (stderr, "Invalid Input\n");
			continue;
		}
		if (prm.error == 1){
			sprintf (wbuf,"quit\n"); 
			for(i = 0; i < children; i++){
				write(parentInFD[i][1], wbuf, strlen(wbuf));
				pid = waitpid(getpid(),&status,0); /*wait for child to terminate*/
			}

			break;
		}

		{
			int n_int = 0; // final = 0;
			double lb = 0, hb = 0;
			double fact;


			n_int = prm.n_intervals / children ;
			fact = (prm.hi_bound - prm.lo_bound)/(prm.n_intervals - 1);
			//fprintf (stderr, "\nChild : %d\n",children);
			for (j = 0; j < children; j++){

				char wbuf[512] = "";
				lb = prm.lo_bound + (double)((j * n_int) * fact);
				hb = prm.lo_bound + (double)(((j+1) * n_int) * fact);

				wbuf[0]=0;
				if (debug){
					sprintf (dBuf, "(%d:%ld) %s %.6g %.6g %d\n",
							level+1,(long)getpid(), prm.fnName, lb, hb, n_int);
					fprintf (stderr, "%s",dBuf);
				}

				sprintf (wbuf, "%s %lf %lf %d\n",prm.fnName, lb, hb, n_int);

				write (parentInFD[j][1], wbuf, strlen(wbuf));

				/*

				//buf[0]='\0';
				strncpy(buf," ",512);
				//fprintf (stderr, "LB : %g\n",lb);
				if (j == children - 1){
				hb = prm.lo_bound + (double)(( prm.n_intervals - 1) * fact);

				sprintf (buf, "%s %lf %lf %d\n",prm.fnName, lb, hb, (prm.n_intervals - final));

				write (parentInFD[j][1], buf, strlen(buf));

				}
				else{
				hb = prm.lo_bound + (double)(n_int *(j+1) - 1) * fact;

				sprintf (buf, "%s %lf %lf %d\n",prm.fnName, lb, hb, n_int);

				write (parentInFD[j][1], buf, strlen(buf));
				}
				final += n_int;
				 */
			}
		}

		result = 0;
		for (i = 0; i < children; i++){
			tmp = 0;
			rbuf[0]=0;
			read(parentRtFD[i][0], rbuf,MAX_BUF );
			tmp = strtod(rbuf,NULL);
			result += tmp;
		}

		wbuf[0]=0;
		sprintf (wbuf,"%lf\n",result);
		write (STDOUT_FILENO, wbuf ,strlen(wbuf));
	}
}

/* Perform integration of trapezoidal rule at leaf node */
void integration (int children, int level, int debug){
	char *line= NULL;
	char wbuf[512] = "";

	while(1){

		Params prm;
		double result = 0;

		prm = getCmdLineInput(level);

		if(prm.error == 1){
			break;
		}

		/*	if (debug){
			sprintf (dBuf, "(%d:%ld) %s %.6g %.6g %d\n",
			level,(long)getpid(), prm.fnName, prm.lo_bound, prm.hi_bound, prm.n_intervals);
			fprintf (stderr, "%s",dBuf);
			}*/
		result = 0;
		/* Compute the trapezoidal result when only the value of flag is 0 and count for entered arguments is proper */
		result = trapezoidal(getFnForString(prm.fnName), prm.lo_bound, prm.hi_bound, prm.n_intervals);

		wbuf[0] = 0;
		sprintf (wbuf, "%lf", result);
		write(STDOUT_FILENO, wbuf, strlen(wbuf));
	}
	free(line);
}

void createProcessTree ( int *childProcess, int childIndex, int level, int debug){

	int i = 0;
	int pid;
	int **parentInFD, **parentRtFD;

	parentInFD = (int **) malloc (childProcess[childIndex] * sizeof (int));
	for (i = 0; i < childProcess[childIndex]; i++){
		parentInFD[i] = (int *) malloc(2 * sizeof (int));
	}

	parentRtFD = (int **) malloc (childProcess[childIndex] * sizeof (int));
	for (i = 0; i < childProcess[childIndex]; i++){
		parentRtFD[i] = (int *) malloc(2 * sizeof(int));
	}

	for ( i = 0; i < childProcess[childIndex]; i++){

		if (pipe (parentInFD[i]) < 0){
			perror("pipe open error");
			exit(1);
		}

		if (pipe (parentRtFD[i]) < 0){
			perror("pipe open error");
			exit(1);
		}

		pid = fork();
		if(pid < 0){
			perror("pipe error");
			exit(1);
		}
		if (pid == 0){
			close (parentInFD[i][1]);
			close (parentRtFD[i][0]);

			if (parentInFD[i][0] != STDIN_FILENO){
				if (dup2(parentInFD[i][0], STDIN_FILENO) < 0){
					perror ("dup failed");
					exit (1);
				}
			}
			if (parentRtFD[i][1] != STDOUT_FILENO){
				if (dup2(parentRtFD[i][1], STDOUT_FILENO) < 0){
					perror ("dup failed");
					exit (1);
				}
			}
			close (parentInFD[i][0]);
			close (parentRtFD[i][1]);

			if (childIndex == level - 1 ){
				integration(childProcess[childIndex], level, debug);
				return;
			}
			else{
				childIndex ++;
				createProcessTree (childProcess, childIndex, level, debug);
			}

			return;
		}
		else{
			close (parentInFD[i][0]);
			close (parentRtFD[i][1]);
		}
	}

	getData(parentInFD, parentRtFD, childProcess[childIndex], childIndex, debug); 
	return;

}

int main (int argc, char *argv[]){

	int i = 0, j = 0, level = 0;
	int *childProcesses;
	int debug = 0;

	if (strcmp (argv[1],"-d") == 0){
		if (argc < 3){
			fprintf (stderr, "\nprj2: illegal input  \nUsage : prj2 [-d] N1 N2 ...\n\n");
			exit (1);
		}

		debug = 1;

		childProcesses = (int *) malloc (argc - 2);
		if (!childProcesses){
			fprintf (stderr, "\nERROR : No memory available\n");
			exit (1);
		}
		for (i = 2; i < argc; i++){
			childProcesses[j++] = atoi (argv[i]);
		}
		level = argc - 2;
	}
	else{
		if (argc < 2){
			fprintf (stderr, "\nprj2: illegal input  \nUsage : prj2 [-d] N1 N2 ...    argc %d\n\n",argc);
			exit (1);
		}
		childProcesses = (int *) malloc (argc - 1);
		if (!childProcesses){
			fprintf (stderr, "\nERROR : No memory available\n");
			exit (1);
		}
		for (i = 1; i < argc; i++){
			childProcesses[j++] = atoi (argv[i]);
		}
		level = argc - 1;
	}

	createProcessTree(childProcesses, 0, level, debug);

	return 0;
}
